package com.remainder.sdk.export.service.impl;

import java.io.*;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.function.Supplier;

import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.UnixStat;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.input.NullInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.pagehelper.PageHelper;
import com.remainder.common.core.exception.BusinessException;
import com.remainder.common.core.utils.StringUtils;
import com.remainder.common.core.utils.poi.ExcelUtil;
import com.remainder.oss.s3.core.OssTemplate;
import com.remainder.sdk.export.config.ExportLogConfig;
import com.remainder.sdk.export.enums.ExportResultLocationEnum;
import com.remainder.sdk.export.service.BatchExport;
import com.remainder.system.api.RemoteConfigService;

import cn.hutool.core.collection.CollectionUtil;

/**
 * 批量导出抽象实现
 *
 * @author qiaomignzhi
 * @date 2023-06-13
 */
public class BatchExportImpl<T> implements BatchExport<T> {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final ExportLogService exportLogSyncServiceImpl;
    private final OssTemplate<Object> ossTemplate;
    private final RemoteConfigService remoteConfigService;

    public BatchExportImpl(ExportLogService exportLogSyncServiceImpl, OssTemplate<Object> ossTemplate,
        RemoteConfigService remoteConfigService) {
        this.exportLogSyncServiceImpl = exportLogSyncServiceImpl;
        this.ossTemplate = ossTemplate;
        this.remoteConfigService = remoteConfigService;
    }

    @Override
    public void startExport(ExportLogConfig config, Supplier<List<T>> cursorSupplier) {
        String batchNumber = null;
        try {
            batchNumber = exportLogSyncServiceImpl.caeateExportLog(config);

            File zipOutName = dealWithData(cursorSupplier, config, batchNumber);
            if (Objects.isNull(zipOutName)) {
                logger.info("AbstractBatchExport export end[{}]导出结果为空", batchNumber);
                exportLogSyncServiceImpl.failUpdate();
                return;
            }
            Map<String, String> uploadResult = uploadData(zipOutName, config, batchNumber);
            executorFinally(uploadResult);
        } catch (Exception e) {
            logger.error("AbstractBatchExport export error[" + batchNumber + "]", e);
            exportLogSyncServiceImpl.failUpdate();
            throw new BusinessException("AbstractBatchExport export error", e);
        }
    }

    @Override
    public File dealWithData(Supplier<List<T>> cursorSupplier, ExportLogConfig config, String batchNumber)
        throws Exception {
        // 当循环体中 检测查询数量为0时 则停止循环
        boolean sign = true;
        List<File> outFiles = new ArrayList<>();
        int pageNum = 1;
        // 分页查询数据写入临时文件中
        while (sign) {
            // 若不需要做导出进度条则无需count
            PageHelper.startPage(pageNum++, 65535, false);
            // 单线程读-下面可并发写入excel文件
            List<T> dataList = cursorSupplier.get();
            if (CollectionUtil.isEmpty(dataList)) {
                sign = false;
                continue;
            }
            String info = config.getExportFileSourceEnum().getInfo();
            File tempFile = File.createTempFile(info + System.currentTimeMillis() + "-", ".xlsx");
            outFiles.add(tempFile);

            try (FileOutputStream stream = new FileOutputStream(tempFile)) {
                ExcelUtil util = new ExcelUtil<>(dataList.get(0).getClass());
                util.exportExcel(stream, dataList, info);
            }
        } ;
        if (CollectionUtil.isEmpty(outFiles)) {
            return null;
        }

        // 将上方处理完毕的 临时文件列表 压缩保存
        File zipOutName = File.createTempFile(batchNumber, ".zip");
        // parallelScatterZipCreator.writeTo中会将线程池给shutdown
        ParallelScatterZipCreator parallelScatterZipCreator =
            new ParallelScatterZipCreator(Executors.newFixedThreadPool(outFiles.size()));
        try (OutputStream outputStream = new FileOutputStream(zipOutName);
            ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream)) {
            zipArchiveOutputStream.setEncoding("UTF-8");
            for (File inFile : outFiles) {
                final InputStreamSupplier inputStreamSupplier = () -> {
                    try {
                        return new FileInputStream(inFile);
                    } catch (FileNotFoundException e) {
                        return new NullInputStream(0);
                    }
                };
                ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(inFile.getName());
                zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
                zipArchiveEntry.setSize(inFile.length());
                zipArchiveEntry.setUnixMode(UnixStat.FILE_FLAG | 436);
                parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
            }
            parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
        }

        return zipOutName;
    }

    @Override
    public Map<String, String> uploadData(File zipFile, ExportLogConfig exportLogConfig, String batchNumber)
        throws IOException {
        String path = getExportEnvironment();
        boolean bol = StringUtils.isEmpty(path);
        Map<String, String> result = new HashMap<>();
        String address;
        String exportType;
        if (bol) {
            address = writerOss(zipFile, exportLogConfig, batchNumber);
            exportType = ExportResultLocationEnum.OSS.getCode();
        } else {
            address = writerLocal(zipFile, path, batchNumber);
            exportType = ExportResultLocationEnum.LOCAL.getCode();
        }
        result.put("exportType", exportType);
        result.put("address", address);

        return result;
    }

    /**
     * 导出任务完结
     *
     * @param uploadResult
     *            {@link BatchExportImpl#uploadData(File, ExportLogConfig, String)}
     */
    @Override
    public void executorFinally(Map<String, String> uploadResult) {
        exportLogSyncServiceImpl.exportLogFinally(uploadResult.get("exportType"), uploadResult.get("address"));
    }

    /**
     * 文件写本地
     *
     * @param zipFile
     *            导出数据
     * @param path
     *            本地路径
     * @param batchNumber
     *            批次号
     * @return 导出全路径
     */
    private String writerLocal(File zipFile, String path, String batchNumber) {
        String zipFileName = zipFile.getName();
        String pathname = path + java.util.UUID.randomUUID().toString().replaceAll("-", "")
            + zipFileName.substring(zipFileName.lastIndexOf("."));
        File file = new File(pathname);
        boolean b = zipFile.renameTo(file);
        if (!b) {
            logger.info("BatchExportImpl writerLocal error[{}][{}]to[{}]", batchNumber, zipFile.getPath(),
                file.getPath());
            throw new BusinessException("");
        }
        return pathname;
    }

    /**
     * 文件写OSS
     *
     * @param zipFile
     *            数据源
     * @param exportLogConfig
     *            导出配置
     * @param batchNumber
     *            批次好
     * @return 上传全路径
     */
    private String writerOss(File zipFile, ExportLogConfig exportLogConfig, String batchNumber) {
        try (FileInputStream input = new FileInputStream(zipFile);) {

            ossTemplate.putObject("bucketName", zipFile.getName(), input);

            return ossTemplate.getObjectURL("bucketName", zipFile.getName(), 60);
        } catch (Exception e) {
            logger.error("BatchExportImpl writerOss error[" + batchNumber + "]", e);
            throw new BusinessException("BatchExportImpl writerOss error[" + batchNumber + "]");
        }
    }

    private String getExportEnvironment() {
        com.remainder.common.core.domain.R<String> r = remoteConfigService.configInfoKey("export_environment");
        if (!r.isSuccess()) {
            throw new BusinessException("BatchExportImpl getExportEnvironment getConfig1 error");
        }
        if ("Y".equalsIgnoreCase(r.getData())) {
            return null;
        }
        com.remainder.common.core.domain.R<String> r1 = remoteConfigService.configInfoKey("export_environment_st");
        if (r1.isSuccess()) {
            return r1.getData();
        }
        throw new BusinessException("BatchExportImpl getExportEnvironment getConfig2 error");
    }
}
